package com.planw.beetl.service;


import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLiteralValue;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.planw.beetl.utils.BeetlSqlConst;
import com.planw.beetl.utils.ConstUtil;
import com.planw.beetl.utils.PsiKt;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.intellij.plugins.markdown.lang.MarkdownTokenTypes;
import org.intellij.plugins.markdown.lang.psi.impl.MarkdownHeader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * @author yanglin
 */
public class BeetlMapperService {

  private static final Logger logger = Logger.getInstance(BeetlMapperService.class);

  private final Project project;
  private final JavaPsiFacade javaPsiFacade;
  private final GlobalSearchScope javaSearchScope;
  private final GlobalSearchScope allSearchScope;
  public BeetlMapperService(Project project) {
    this.project = project;
    this.javaPsiFacade = JavaPsiFacade.getInstance(project);
    this.javaSearchScope = GlobalSearchScope.getScopeRestrictedByFileTypes(
        GlobalSearchScope.allScope(project), JavaFileType.INSTANCE);

    this.allSearchScope = GlobalSearchScope.allScope(project);

  }

  public static BeetlMapperService getInstance(@NotNull Project project) {
    return project.getService(BeetlMapperService.class);
  }


  public boolean isBeetlMapper(PsiClass psiClass) {
    if (!psiClass.isInterface()) {
      return false;
    } else {
      String baseMapperClassName = ConstUtil.getBaseMapperClassName(psiClass.getProject());
      for(PsiClass parentInterface = (PsiClass)ArrayUtil.getFirstElement(psiClass.getInterfaces()); Objects.nonNull(parentInterface); parentInterface = (PsiClass)ArrayUtil.getFirstElement(parentInterface.getInterfaces())) {
        if (StringUtil.equals(parentInterface.getQualifiedName(), baseMapperClassName)) {
          return true;
        }
      }

      return false;
    }
  }

  /**
   * 获取所有继承了BaseMapper的接口
   */
  public Collection<PsiClass> getAllMappers(Module module) {
    logger.info("开始获取所有继承了BaseMapper的接口:" + module.getName());
    if (Objects.isNull(module)) {
      return Collections.emptyList();
    } else {
      BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(module.getProject());
      PsiClass baseMapperPsiClass = beetlSqlConst.BASE_MAPPER_PSI_CLASS;
      return ClassInheritorsSearch.search(baseMapperPsiClass, GlobalSearchScope.moduleScope(module), true).findAll();
    }
  }

  public Map<PsiMethod, PsiElement> getMethodAndSqlIdMap(PsiFile sqlFile, PsiClass psiClass,
      PsiMethod psiMethod) {

    logger.info("开始获取markdown文件的Header内容与Mapper接口中方法一一对应的Map");
    if (psiClass == null) {
      return Collections.emptyMap();
    } else {
      PsiMethod[] methods = psiMethod == null ? psiClass.getMethods() : new PsiMethod[]{psiMethod};
      if (methods.length == 0) {
        return Collections.emptyMap();
      } else {
        Map<String, PsiElement> sqlIdMap = new HashMap();

        for(MarkdownHeader markdownHeader : PsiTreeUtil.findChildrenOfType(sqlFile.getOriginalFile(), MarkdownHeader.class)) {
          ASTNode node = markdownHeader.getNode().findChildByType(MarkdownTokenTypes.SETEXT_CONTENT);
          if (node != null) {
            sqlIdMap.put(node.getText(), node.getPsi());
          }
        }

        logger.info("当前md文件的所有Header：" + sqlIdMap);
        Map<PsiMethod, PsiElement> methodAndSqlIdMap = new HashMap();

        for(PsiMethod method : methods) {
          String name = method.getName();
          PsiElement sqlId = (PsiElement)sqlIdMap.get(name);
          if (sqlId != null) {
            methodAndSqlIdMap.put(method, sqlId);
          }
        }

        logger.info("最终一一对应的Map：" + methodAndSqlIdMap);
        return methodAndSqlIdMap;
      }
    }
  }

  public PsiFile findSqlFile(PsiClass mapperPsiClass) {
    String sqlFileName = this.guessMdFilePathByMapperClass(mapperPsiClass);
    return StringUtils.isBlank(sqlFileName) ? null : this.getBeetlSqlFile(ModuleUtil.findModuleForPsiElement(mapperPsiClass), sqlFileName);
  }

  @Nullable
  public String guessMdFilePathByMapperClass(PsiClass mapperPsiClass) {
    if (!this.isBeetlMapper(mapperPsiClass)) {
      return null;
    } else {
      BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(mapperPsiClass.getContainingFile().getOriginalFile());
      String sqlFileName = null;
      PsiAnnotation sqlResource = mapperPsiClass.getAnnotation(beetlSqlConst.SQL_RESOURCE_BEETL_CLASS);
      if (sqlResource != null) {
        PsiAnnotationMemberValue value = sqlResource.findDeclaredAttributeValue("value");
        PsiLiteralValue psiLiteralValue = (PsiLiteralValue)value;
        sqlFileName = psiLiteralValue != null ? (String)psiLiteralValue.getValue() : null;
        logger.info("存在SqlResource注解，value值为：" + sqlFileName);
      } else {
        PsiClassType[] parentInterfaces = mapperPsiClass.getExtendsListTypes();
        PsiClassType firstParentInterface = (PsiClassType)ArrayUtil.getFirstElement(parentInterfaces);
        if (Objects.isNull(firstParentInterface)) {
          return null;
        }

        sqlFileName = firstParentInterface.getParameters()[0].getPresentableText();
        logger.info("无SqlResource注解，将通过继承的BaseMapper接口编写的泛型猜测文件名：" + sqlFileName);
      }

      return sqlFileName;
    }
  }

  public PsiFile getBeetlSqlFile(Module module, String className) {
    if (StringUtils.isBlank(className)) {
      return null;
    } else {
      String sqlFileName = StringUtils.uncapitalize(className).replaceAll("\\.", "/");
      PsiFile sqlFile = this.loadSqlFile(module, sqlFileName + ".md");
      if (sqlFile != null) {
        return sqlFile;
      } else {
        sqlFile = this.loadSqlFile(module, sqlFileName + ".sql");
        return sqlFile;
      }
    }
  }

  public boolean isPageRequest(String pageRequestClassName, PsiParameter parameter) {
    String className = PsiKt.getClassName(parameter.getType());
    return StringUtils.equals(pageRequestClassName, className);
  }

  public boolean isPageRequest(PsiParameter parameter) {
    String pageRequestClassName = ConstUtil.getPageRequestClassName(parameter.getContainingFile().getOriginalFile());
    String className = PsiKt.getClassName(parameter.getType());
    return StringUtils.equals(pageRequestClassName, className);
  }

  public PsiAnnotation getParamAnnotation(PsiParameter parameter) {
    String paramAnnotationClassName = ConstUtil.getParamAnnotationClassName(parameter.getContainingFile().getOriginalFile());
    return parameter.getAnnotation(paramAnnotationClassName);
  }

  public String getParamAnnotationValue(String paramAnnotationClassName, PsiParameter parameter) {
    PsiAnnotation annotation = parameter.getAnnotation(paramAnnotationClassName);
    return annotation == null ? null : ((String)Optional.ofNullable(annotation.findDeclaredAttributeValue("value")).map(PsiElement::getText).orElse("")).replaceAll("\"", "");
  }

  public String getParamAnnotationValue(PsiParameter parameter) {
    PsiAnnotation annotation = this.getParamAnnotation(parameter);
    return annotation == null ? null : ((String)Optional.ofNullable(annotation.findDeclaredAttributeValue("value")).map(PsiElement::getText).orElse("")).replaceAll("\"", "");
  }

  private PsiFile loadSqlFile(Module module, String sqlFilePath) {
    String fileName = Paths.get(sqlFilePath).getFileName().toString();
    Collection<VirtualFile> mdVirtualFiles = FilenameIndex.getVirtualFilesByName(fileName, GlobalSearchScope.moduleScope(module));
    PsiFile psiFile = null;

    for(VirtualFile mdFile : mdVirtualFiles) {
      if (mdFile.getPath().endsWith(sqlFilePath)) {
        psiFile = PsiUtil.getPsiFile(module.getProject(), mdFile);
        break;
      }
    }

    logger.info("在当前模块中找文件：" + sqlFilePath + "；查找结果：" + psiFile);
    return psiFile;
  }

}

